﻿// MIT License

// Copyright (c) 2021-2023 1024


using Furion.DataEncryption;
using Furion.DynamicApiController;
using Furion.EventBus;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Hosting;

namespace Kyx.Application.HtAdminServices;

[ApiDescriptionSettings("HtAdmin", Name = "HtAccount")]
[MaApi(area: "API", controller: "HtAccount", controllerDescription: "账号登入/登出", PCode = "API")]
[IgnoreAntiforgeryToken]
public class HtAccountService : IHtAccountService, ITransient, IDynamicApiController
{
    private readonly ILogger<HtAccountService> _logger;
    private readonly IRepository<SysUser> _repository;
    private readonly ISysCacheService _sysCacheService;
    private readonly Lazy.Captcha.Core.ICaptcha _captcha;
    private readonly ISysLogService _sysLogService;
    public HtAccountService(ILogger<HtAccountService> logger

     , ISysCacheService sysCacheService,
IRepository<SysUser> repository,
Lazy.Captcha.Core.ICaptcha captcha,
ISysLogService sysLogService)
    {
        _logger = logger;
        _sysCacheService = sysCacheService;
        _repository = repository;
        _captcha = captcha;
        _sysLogService = sysLogService;
    }

    /// <summary>
    /// 校验登录条件
    /// </summary>
    /// <param name="input">输入</param>
    /// <param name="loginSource">登录来源</param>
    private async Task<SysUser> ValidLoginAsync(HtLoginInput input, string loginSource)
    {
        //验证是否允许登录（账号登录失败次数过多）
        if (!input.Account.IsAllowLogin(loginSource, out string errorMsg))
        {
            throw Oops.Oh(errorMsg);
        }

        #region 解密提交的密码

        var appinfo = AppEx.GetConfig<AppInfoOptions>();
        var LoginPriveKey = appinfo.HtAdminLoginRsaPrivate.RsaKeyFilter();
        var LoginPublicKey = appinfo.HtAdminLoginRsaPublic.RsaKeyFilter();
        LoginRsa rsa = new LoginRsa(LoginPriveKey, LoginPublicKey);
        //解密前端提交的密钥
        var encryptPwd = rsa.Decrypt(input.PassWord).ToPBKDF2();

        #endregion 解密提交的密码


        var user = await _repository
                .Where(!App.HostEnvironment.IsDevelopment(), u => u.Pwd.Equals(encryptPwd))
          //.FirstOrDefaultAsync(o => (o.UserType == UserType.系统维护 || o.UserType == UserType.超级管理员 || o.UserType == UserType.管理员) && o.Account == input.Account);
          .FirstOrDefaultAsync(o => o.Account.ToLower() == input.Account.ToLower());
        if (user == null)
        {
            input.Account.LoginFail(loginSource);
            throw Oops.Bah("账号或密码错误");
        }

        if (user == null)
        {
            //记录账号登录失败
            input.Account.LoginFail(loginSource);
            throw Oops.Bah($"账号或密码错误");
        }

        if (user.IsEnable == false)
        {//记录账号登录失败
            input.Account.LoginFail(loginSource);
            throw Oops.Bah("账号或密码错误");
        }

        var client = AppEx.GetWebInfo();

        user.LastLoginIp = App.HttpContext.GetIpV4();
        user.LastLoginTime = DateTimeOffset.Now;
        user.LastLoginUserAgent = client.UserAgent;

        // 更新用户最后登录Ip和时间
        await _repository.UpdateIncludeAsync(user, new[] { nameof(SysUser.LastLoginIp), nameof(SysUser.LastLoginTime), nameof(SysUser.LastLoginUserAgent) });

        await _sysLogService.AddVisLogAsync(user, LoginType.LOGIN);

        //设置登录缓存
        await _sysCacheService.SetLoginAdminUserCache(user.Id);

        //账号登录成功，移除登录失败缓存数据
        input.Account.LoginSuccess(loginSource);


        return user;

    }

    /// <summary>
    /// 管理登录
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [ApiExplorerSettings(IgnoreApi = true)]
    public async Task<HtLoginOut> Login(HtLoginInput input)
    {

        ////验证码
        //if (!CaptchaHelper.VerifyCode(input.VerifyCode, verifyKey: "HtAdmin"))
        //{
        //    input.Account.LoginFail("HtAdmin");
        //    throw Oops.Oh("验证码不正确!");
        //}

        //#region 图片验证码
        //var verifyCodeId = App.HttpContext.GetSessionValue("HtAdmin");
        //if (!_captcha.Validate(verifyCodeId, input.VerifyCode))
        //{

        //    input.Account.LoginFail("HtAdmin");
        //    throw Oops.Bah("验证码不正确!");
        //}
        //#endregion


        #region 滑动验证码

        if (string.IsNullOrEmpty(input.CaptchaId))
        {
            input.Account.LoginFail("HtAdmin");
            throw Oops.Bah("滑动验证失败!");
        }

        var isPass = Caches.Get<bool>($"{input.CaptchaId}_captchaId");

        //只要请求过了，就把这个key移除(防止一key多用)
        Caches.Remove($"{input.CaptchaId}_captchaId");

        if (!isPass)
        {
            input.Account.LoginFail("HtAdmin");
            throw Oops.Bah("滑动验证失败!");
        }

        #endregion

        var user = await ValidLoginAsync(input, "HtAdmin");

        #region claims

        var identity = new
            ClaimsIdentity(AuthorizeType.AdminAuthenticationScheme);//一定要声明AuthenticationScheme
        identity.AddClaim(new Claim(ClaimConst.Claim_Account, user.Account?.ToString() ?? ""));
        identity.AddClaim(new Claim(ClaimConst.Claim_UserId, user.Id.ToString()));
        identity.AddClaim(new Claim(ClaimConst.Claim_Name, user.Nick?.ToString() ?? ""));
        //identity.AddClaim(new Claim(ClaimConst.Claim_Account, dbmodel.Account?.ToString() ?? ""));

        identity.AddClaim(new Claim("AreaType", "HtAdmin"));
        LoginApiSwagger();

        await App.HttpContext.SignInAsync(identity.AuthenticationType,
            new ClaimsPrincipal(identity),
            new AuthenticationProperties
            {
                IsPersistent = input.Remember,//false 览器关闭时清除会话cookie
                RedirectUri = "/HtAdmin/Home/Index",
            });

        #endregion claims

        return user.Adapt<HtLoginOut>(); ;
    }


    /// <summary>
    /// 前端Api接口登录
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [MaPermission(name: "管理登录", actions: "Login")]
    [ApiDescriptionSettings(Name = "ApiLogin")]
    [AllowAnonymous]
    public async Task<HtLoginOut> ApiLogin(HtLoginInput input)
    {
        ////验证码
        //if (!CaptchaHelper.VerifyCode(input.VerifyCode, verifyKey: "HtAdmin"))
        //{
        //    throw Oops.Oh("验证码不正确!");
        //}
        //验证是否允许登录（账号登录失败次数过多）

        var user = await ValidLoginAsync(input, "AbcApi");

        user.LastLoginTime = DateTimeOffset.Now;


        #region 设置登录权限
        // token
        var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>()
            {
                { ClaimConst.Claim_UserId, user.Id.ToString()},  // 存储Id
                { ClaimConst.Claim_Account,user.Account?.ToString() ?? "" }, // 存储用户名
                { ClaimConst.Claim_Name,user.Nick?.ToString() ?? "" }, // 存储用户名
            });

        // 获取刷新 token
        var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken, 43200); // 第二个参数是刷新 token 的有效期（分钟），默认三十天

        // 设置响应报文头
        App.HttpContext.Response.Headers["access-token"] = accessToken;
        App.HttpContext.Response.Headers["x-access-token"] = refreshToken;
        #endregion

        return user.Adapt<HtLoginOut>(); ;
    }

    /// <summary>
    /// 登录API Swagger 可视化页面
    /// </summary>
    /// <returns></returns>
    public void LoginApiSwagger()
    {
        //设置可访问 swagger index.html 可看到API权限
        CookieHelper.Set("A1", "HtAdmin", "ERGSRkSe5g8e89twEDfgdFGSrth45t5y");
    }

    /// <summary>
    /// 获取当前登录用户
    /// </summary>
    /// <returns></returns>
    [MaPermission(name: "获取当前登录用户", actions: "GetLoginUser")]
    public async Task<HtUserOut> GetLoginUser()
    {
        var user = await _sysCacheService.GetLoginAdminUser();
        return user.Adapt<HtUserOut>();
    }

    /// <summary>
    /// 获取后台首页左侧菜单列表
    /// </summary>
    /// <param name="area">区域</param>
    /// <returns></returns>
    [MaPermission(name: "获取后台首页左侧菜单列表", actions: "GetMenus")]
    public async Task<List<PearMenuItemOut>> GetMenus(string area)
    {
        var entities = await _repository.Change<SysMenu>().DetachedEntities.ToListAsync();
        var dbmenus = entities.Where(o => o.Area != null && o.Area.ToLower() == area.ToLower()).ToList();
        //获取当前登录用户
        var user = await _sysCacheService.GetLoginAdminUser();
        if (user != null && user.Account.ToLower().Trim() == "developer".ToLower().Trim())
        {
            //开发账号，不受限制，方便开发测试，不然折腾
        }
        else
        {
            dbmenus = entities.Where(o => o.IsEnable && o.IsShow).ToList();
        }
        //如果是系统管理员或开发者，跳过
        if (user != null && user.Account.ToLower().Trim() != "administrator".ToLower().Trim() && user.Account.ToLower().Trim() != "developer".ToLower().Trim())
        {
            var rolemenus = new List<SysMenu>();
            foreach (var role in user.Roles)
            {
                rolemenus.AddRange(role.Menus);
            }
            //循环遍历，移除没有权限的菜单
            foreach (var menuitem in dbmenus.Where(o => o.MenuType == MenuType.菜单).Select(o => new { o.Id, o.Code }).ToList())
            {
                if (rolemenus.All(o => o.Id != menuitem.Id))
                {
                    //未从用户角色菜单中找到
                    var menu = dbmenus.First(o => o.Id == menuitem.Id);
                    //验证是否有下级功能
                    if (rolemenus.Any(o => o.PCode == menu.Code)) continue;//跳过，已有菜单权限

                    //没有下级功能，且没有菜单权限，移除
                    dbmenus.Remove(menu);
                }
            }
        }

        var pearMenus = dbmenus.Adapt<List<PearMenuItem>>();

        var rmenus = pearMenus.GetMenus(pid: area);
        //为了提高用户体验，系统维护人员可以看到空目录方便修改优化
        if (user != null && user.UserType != UserType.系统维护)
        {
            //遍历，移除没有下级的空目录
            foreach (var item in rmenus.ToList())
            {
                //跳过主菜单，没有上级的菜单
                if (item.type == 1) continue;
                // 如果不是“系统维护”账号developer 移除开发工具
                if (item.children == null || item.children.Count <= 0 || item.id.ToString() == "HtAdmin_Developer")
                {
                    rmenus.Remove(item);
                }
            }
        }
        //排除后台首页菜单
        rmenus = rmenus.Where(o => o.id.ToString() != "HtAdmin_Home_Index").ToList();
        return rmenus;
    }
}